﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using Ue.CQRS.Events;
using Ue.CQRS.Repositories;

namespace Ue.CQRS.DistributionManager
{
    public class Distributor : IDistributor
    {
        public event EventHandler<DispatchEventArgs> Dispatching;
        public event EventHandler<DispatchEventArgs> DispatchFailed;
        public event EventHandler<DispatchEventArgs> Dispatched;

        private IRepository<string, ConcurrentQueue<Action<IEvent>>> registrationHandles;

        public Distributor(IRepository<string, ConcurrentQueue<Action<IEvent>>> registrationHandles)
        {
            this.registrationHandles = registrationHandles;
        }

        public void Register(string eventName, Action<IEvent> handler)
        {
            if (string.IsNullOrWhiteSpace(eventName))
            {
                throw new DomainException("eventName不能为null或empty");
            }

            if (handler == null)
            {
                throw new ArgumentNullException("handler");
            }

            var predicates = this.GetOrAdd(eventName);

            Action<dynamic> predicate = domainEvent =>
            {
                var dispatchEventArgs = new DispatchEventArgs(domainEvent, new Action(() => handler(domainEvent)));

                this.OnDispatching(dispatchEventArgs);
                try
                {
                    handler(domainEvent);
                    this.OnDispatched(dispatchEventArgs);
                }
                catch
                {
                    this.OnDispatchFailed(dispatchEventArgs);
                }
            };

            if (!predicates.Contains(predicate))
            {
                predicates.Enqueue(predicate);
            }
        }

        public void Dispatch(IEvent @event)
        {
            var predicates = this.GetOrAdd(@event.Name);

            foreach (var item in predicates)
            {
                item(@event);
            }
        }

        #region event Handlers（Dispatching/DispatchFailed/Dispatched）

        public virtual void OnDispatching(DispatchEventArgs e)
        {
            this.OnClick(this.Dispatching, e);
        }

        public virtual void OnDispatchFailed(DispatchEventArgs e)
        {
            this.OnClick(this.DispatchFailed, e);
        }

        public virtual void OnDispatched(DispatchEventArgs e)
        {
            this.OnClick(this.Dispatched, e);
        }

        private void OnClick(EventHandler<DispatchEventArgs> handler, DispatchEventArgs e)
        {
            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion

        private ConcurrentQueue<Action<IEvent>> GetOrAdd(string eventName)
        {
            return this.registrationHandles.GetOrAdd(eventName, key => new ConcurrentQueue<Action<IEvent>>());
        }
    }
}
